#ifndef _RING_BUFFER_H_
#define _RING_BUFFER_H_

#ifdef __cplusplus
extern "C"{
#endif

#include <stdint.h>

typedef enum {
   ELEMENT_TYPE_U8 = 0,
	ELEMENT_TYPE_U32
} element_type_t;	
/**
 * Ring buffer type.
 *
 * The buffer is empty when head == tail.
 *
 * The buffer is full when the head is one byte in front of the tail,
 * modulo buffer length.
 *
 * One byte is left free to distinguish empty from full. */
typedef struct ring_buffer {
	volatile void *buf; /**< Buffer items are stored into */
	element_type_t type;
	volatile uint16_t head;         /**< Index of the next item to remove */
	volatile uint16_t tail;         /**< Index where the next item will get inserted */
	volatile uint16_t size;         /**< Buffer capacity */
	volatile uint16_t count;
} ring_buffer;

/**
 * Initialise a ring buffer.
 *
 *  @param rb   Instance to initialise
 *
 *  @param size Number of items in buf.  The ring buffer will always
 *              leave one element unoccupied, so the maximum number of
 *              elements it can store will be size - 1.  Thus, size
 *              must be at least 2.
 *
 *  @param buf  Buffer to store items into
 */
static inline void rb_init(ring_buffer *rb, uint16_t size, void *buf, element_type_t type) {
	rb->head = 0;
	rb->tail = 0;
	rb->count = 0;
	rb->size = size;
	rb->buf = buf;
	rb->type = type;
}

/**
 * @brief Return the number of elements stored in the ring buffer.
 * @param rb Buffer whose elements to count.
 */
static inline uint16_t rb_full_count(ring_buffer *rb)
{
	__IO ring_buffer *arb = rb;
	return arb->count;
}

/**
 * @brief Returns true if and only if the ring buffer is full.
 * @param rb Buffer to test.
 */
static inline uint8_t rb_is_full(ring_buffer *rb)
{
	if (rb->count == rb->size)
		return 1;
	return 0;
}

/**
 * @brief Returns true if and only if the ring buffer is empty.
 * @param rb Buffer to test.
 */
static inline uint8_t rb_is_empty(ring_buffer *rb)
{
	if (rb->count == 0)
		return 1;
	return 0;
}

/**
 * Append element onto the end of a ring buffer.
 * @param rb Buffer to append onto.
 * @param element Value to append.
 */
static inline uint8_t rb_insert(ring_buffer *rb, void *p_element) {
	if (rb->count == rb->size)
		return 0;
	else
	{
		switch (rb->type) {
			case ELEMENT_TYPE_U8:
				((uint8_t *)rb->buf)[rb->tail++] = *(uint8_t *)p_element;
				break;
			case ELEMENT_TYPE_U32:
				((uint32_t *)rb->buf)[rb->tail++] = *(uint32_t *)p_element;
				break;
			default:
				break;
		}
		rb->count++;
		if (rb->tail == rb->size)
			rb->tail = 0;
	}
	return 1;
}

/**
 * @brief Remove and return the first item from a ring buffer.
 * @param rb Buffer to remove from, must contain at least one element.
 */
static inline uint8_t rb_remove(ring_buffer *rb, void *p_element)
{
	if (0 == rb->count)
		return 0;
	switch (rb->type) {
		case ELEMENT_TYPE_U8:
			*(uint8_t *)p_element = ((uint8_t *)rb->buf)[rb->head++];
			break;
		case ELEMENT_TYPE_U32:
			*(uint32_t *)p_element = ((uint32_t *)rb->buf)[rb->head++];
			break;
		default:
			break;
	}
	rb->count--;
	if (rb->head == rb->size)
		rb->head = 0;
	return 1;
}

/**
 * @brief Append an item onto the end of a non-full ring buffer.
 *
 * If the buffer is full, removes its first item, then inserts the new
 * element at the end.
 *
 * @param rb Ring buffer to insert into.
 * @param element Value to insert into ring buffer.
 * @return On success, returns -1.  If an element was popped, returns
 *         the popped value.
 */
static inline void rb_push_insert(ring_buffer *rb, void *p_element_in, void *p_element_out) {
	if (rb_is_full(rb)) {
		rb_remove(rb, p_element_out);
	}
	rb_insert(rb, p_element_in);
}

/**
 * @brief Discard all items from a ring buffer.
 * @param rb Ring buffer to discard all items from.
 */
static inline void rb_reset(ring_buffer *rb) {
	rb->tail = rb->head;
	rb->count = 0;
}

#ifdef __cplusplus
} // extern "C"
#endif

#endif
